#include "strclinonb.h"
#include <define.h>
#include <error.h>
#include <sockfunc.h>
#include <wrapfunc.h>
#include <utils.h>

#define MAX_FD(a, b) ((a) > (b) ? (a) : (b))

static void
setnonblock(int fd) {
    int flag = Fcntl(fd, F_GETFL, 0);
    flag |= O_NONBLOCK;
    Fcntl(fd, F_SETFL, flag);
}

/*
标准输入读取内容，发送给服务器，
接收服务器数据后，写入到标准输出。
问题：
 在从控制台读取完数据后，如何发送完数据后才关闭套接字？
 缓冲区的读写控制，参考 to 和 from 变量？
**/
void 
str_cli(int sockfd) {
    int infd = STDIN_FILENO, outfd = STDOUT_FILENO;
    setnonblock(sockfd);
    setnonblock(infd);
    setnonblock(outfd);

    fd_set rset, wset;
    int maxfdp1 = MAX_FD(MAX_FD(infd, outfd), sockfd) + 1;

    char to[MAX_LINE]; // 存放标准输入读取的内容，用于发送给服务器
    char *toiptr, *tooptr;
    char from[MAX_LINE]; // 存放接收服务器的数据，用于写到标准输出
    char *fromiptr, *fromoptr;

    toiptr = tooptr = to;
    fromiptr = fromoptr = from;

    ssize_t ret;
    int stdineof = 0, deal_left_data = 0;
    for(;;) {
        FD_ZERO(&rset);
        FD_ZERO(&wset);
        // 每个 fd 满足条件时才添加到 set 中
        if (fromiptr < &from[MAX_LINE]) // 还有接收来自服务器的剩余空间
            FD_SET(sockfd, &rset);
        if (tooptr != toiptr) // 还有未发送到服务器的数据
            FD_SET(sockfd, &wset);
        if (!stdineof && toiptr < &to[MAX_LINE]) // 还有接收来自标准输入的剩余空间
            FD_SET(infd, &rset);
        if (fromoptr != fromiptr) // 还有未写到标准输出的数据
            FD_SET(outfd, &wset);

        Select(maxfdp1, &rset, &wset, NULL, NULL);

        // 下面 4 个 FD_ISSET 处理应该相互独立的，可以随意被调整位置
        if (FD_ISSET(infd, &rset)) {
            ret = read(infd, toiptr, &to[MAX_LINE] - toiptr);
            if (ret > 0) {
                fprintf(stderr, "%s: read %zd bytes on stdin\n", gf_time(), ret);
                toiptr += ret;
                FD_SET(sockfd, &wset);

            } else if (ret == 0) {
                fprintf(stderr, "%s: EOF on stdin\n", gf_time());
                stdineof = 1;
                if (toiptr == tooptr)
                    Shutdown(sockfd, SHUT_WR);

            } else {
                if (errno != EWOULDBLOCK)
                    error_dump("fail to read infd");
            }
        }
        if (FD_ISSET(sockfd, &wset)) {
            ret = write(sockfd, tooptr, toiptr - tooptr);
            if (ret >= 0) {
                fprintf(stderr, "%s: write %zd bytes to socket\n", gf_time(), ret);
                tooptr += ret;
                if (tooptr == toiptr) {
                    tooptr = toiptr = to;
                    if (stdineof)
                        Shutdown(sockfd, SHUT_WR);
                }

            } else {
                if (errno != EWOULDBLOCK)
                    error_dump("fail to write sockfd");
            }
        }
        if (FD_ISSET(outfd, &wset)) {
            ret = write(outfd, fromoptr, fromiptr - fromoptr);
            if (ret >= 0) {
                fprintf(stderr, "%s: write %zd bytes to stdout\n", gf_time(), ret);
                fromoptr += ret;
                if (fromoptr == fromiptr) {
                    fromoptr = fromiptr = from;
                    if (deal_left_data) {
                        fprintf(stderr, "%s: all left data has been dealed\n", gf_time());
                        break;
                    }
                }

            } else {
                if (errno != EWOULDBLOCK)
                    error_dump("fail to write outfd");
            }
        }
        if (FD_ISSET(sockfd, &rset)) {
            ret = read(sockfd, fromiptr, &from[MAX_LINE] - fromiptr);
            if (ret > 0) {
                fprintf(stderr, "%s: read %zd bytes from socket\n", gf_time(), ret);
                fromiptr += ret;
                FD_SET(outfd, &wset);

            } else if (ret == 0) {
                fprintf(stderr, "%s: EOF on socket\n", gf_time());
                if (!stdineof) {
                    error_quit("%s: server terminated prematurely", gf_time());
                } else if (fromiptr == fromoptr) {
                    fprintf(stderr, "%s: no more data to write to stdout\n", gf_time());
                    break;
                } else {
                    deal_left_data = 1;
                    fprintf(stderr, "%s: deal left data %zd bytes\n", gf_time(), fromiptr - fromoptr);
                }

            } else {
                if (errno != EWOULDBLOCK)
                    error_dump("fail to read sockfd");
            }
        }
    }
}

